package com.example.houseitemmanagebackend.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import lombok.extern.slf4j.Slf4j;

import java.time.Duration;

/**
 * Redis配置类
 */
@Configuration
@EnableCaching
@Slf4j
public class RedisConfig extends CachingConfigurerSupport {

        /**
         * 配置自定义的Jackson2JsonRedisSerializer以支持Java 8日期时间类型
         */
        private Jackson2JsonRedisSerializer<Object> getJackson2JsonRedisSerializer() {
                Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);

                ObjectMapper objectMapper = new ObjectMapper();
                // 指定要序列化的域，field,get和set,以及修饰符范围，ANY表示包括private和public
                objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
                // 指定序列化输入的类型，类必须是非final修饰的
                objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                                ObjectMapper.DefaultTyping.NON_FINAL);
                // 添加Java 8日期时间模块
                objectMapper.registerModule(new JavaTimeModule());
                // 禁用日期时间类型作为时间戳序列化
                objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

                serializer.setObjectMapper(objectMapper);
                return serializer;
        }

        /**
         * 配置RedisTemplate
         */
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
                RedisTemplate<String, Object> template = new RedisTemplate<>();
                template.setConnectionFactory(connectionFactory);

                // 使用StringRedisSerializer来序列化和反序列化redis的key值
                template.setKeySerializer(new StringRedisSerializer());

                // 使用自定义的Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
                Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = getJackson2JsonRedisSerializer();
                template.setValueSerializer(jackson2JsonRedisSerializer);

                // Hash的key也采用StringRedisSerializer的序列化方式
                template.setHashKeySerializer(new StringRedisSerializer());
                template.setHashValueSerializer(jackson2JsonRedisSerializer);

                template.afterPropertiesSet();
                return template;
        }

        /**
         * 配置缓存管理器
         */
        @Bean
        public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
                // 生成一个默认配置，通过config对象即可对缓存进行自定义配置
                RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                                // 设置缓存的默认过期时间，也是使用Duration设置
                                .entryTtl(Duration.ofMinutes(30))
                                // 设置key为string序列化
                                .serializeKeysWith(
                                                RedisSerializationContext.SerializationPair
                                                                .fromSerializer(new StringRedisSerializer()))
                                // 设置value为json序列化，使用自定义序列化器支持Java 8日期时间类型
                                .serializeValuesWith(RedisSerializationContext.SerializationPair
                                                .fromSerializer(getJackson2JsonRedisSerializer()))
                                // 不缓存空值
                                .disableCachingNullValues();

                return RedisCacheManager.builder(connectionFactory)
                                .cacheDefaults(config)
                                .transactionAware()
                                .build();
        }

        /**
         * 自定义缓存异常处理
         * 当Redis缓存服务不可用时，让Spring优雅地回退到直接执行目标方法（即直接查询数据库）
         */
        @Override
        public CacheErrorHandler errorHandler() {
                return new CustomCacheErrorHandler();
        }

        /**
         * 自定义缓存异常处理器
         * 继承自SimpleCacheErrorHandler，在获取缓存和放入缓存的时候，捕获并记录异常，而不是抛出异常
         */
        public static class CustomCacheErrorHandler extends SimpleCacheErrorHandler {

                @Override
                public void handleCacheGetError(RuntimeException exception, org.springframework.cache.Cache cache,
                                Object key) {
                        log.error("Redis缓存获取失败，key: {}，异常: {}，正在回退到数据库查询", key, exception.getMessage());
                        // 不抛出异常，允许回退到数据库查询
                }

                @Override
                public void handleCachePutError(RuntimeException exception, org.springframework.cache.Cache cache,
                                Object key, Object value) {
                        log.error("Redis缓存更新失败，key: {}，异常: {}，数据库操作仍会继续", key, exception.getMessage());
                        // 不抛出异常，允许继续执行
                }

                @Override
                public void handleCacheEvictError(RuntimeException exception, org.springframework.cache.Cache cache,
                                Object key) {
                        log.error("Redis缓存清除失败，key: {}，异常: {}，数据库操作仍会继续", key, exception.getMessage());
                        // 不抛出异常，允许继续执行
                }

                @Override
                public void handleCacheClearError(RuntimeException exception, org.springframework.cache.Cache cache) {
                        log.error("Redis缓存清空失败，异常: {}，数据库操作仍会继续", exception.getMessage());
                        // 不抛出异常，允许继续执行
                }
        }
}